package com.want.service;

import com.want.model.dto.Result;
import com.want.model.dto.ResultUtil;
import com.want.models.entity.User;
import com.want.models.entity.UserRole;
import com.want.models.entity.UserRoleBo;
import com.want.repository.UserRepository;
import com.want.repository.UserRoleRepository;
import com.want.req.base.PageReqDto;
import com.want.req.user.dto.LoginReqDto;
import com.want.req.user.dto.RegisterReqDto;
import com.want.req.user.dto.UpdateUserReqDto;
import com.want.resp.user.dto.*;
import com.want.util.MyBeanUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author WangZhiJian
 * @since 2021/11/21
 */
@Slf4j
@Service
public class UserService {
    @Resource
    private UserRepository userRepository;
    @Value("${spring.application.name}")
    private String serviceName;
    @Resource
    private UserRoleRepository userRoleRepository;

    public Mono<Long> count(){
        return userRepository.count();
    }

    public Mono<Result<UserDto>> selectById(Integer id){
        return userRepository.findById(id)
                .map(u -> MyBeanUtil.copyProperties(u, UserDto.class))
                .map(ResultUtil::ofSuccess)
                .switchIfEmpty(Mono.defer(() -> Mono.just(ResultUtil.ofNotFound("该用户不存在！", UserDto.class))));
    }

    @Transactional(rollbackFor = Exception.class)
    public Mono<Result<RegisterRespDto>> save(RegisterReqDto registerReqDto){
        User user = new User();
        user.setRegisterTime(LocalDateTime.now());
        BeanUtils.copyProperties(registerReqDto,user);
        Mono<User> byEmail = userRepository.findByEmail(registerReqDto.getEmail());
        return byEmail
                .flatMap(dbUser -> Mono.defer(() -> Mono.just(ResultUtil.ofFail("邮箱已经被注册！",RegisterRespDto.class))))
                .switchIfEmpty(Mono.defer(() -> userRepository.save(user)).flatMap(u -> {
                    List<UserRoleDto> userRoleList = registerReqDto.getUserRoleList();
                    log.info("userRoleList is {}",userRoleList);
                    List<UserRole> userRoles = Optional.ofNullable(userRoleList)
                            .map(list -> list.stream().map(roleItem -> {
                                UserRole userRole = new UserRole();
                                userRole.setRoleId(roleItem.getRoleId());
                                userRole.setUid(u.getId());
                                return userRole;
                            }).collect(Collectors.toList()))
                            .orElseGet(Collections::emptyList);
                    return Mono.just(userRoles)
                            .filter(CollectionUtils::isNotEmpty)
                            .flatMapMany(userRoleRepository::saveAll)
                            .collectList()
                            .then(Mono.fromSupplier(() -> {
                                RegisterRespDto registerRespDto = new RegisterRespDto();
                                BeanUtils.copyProperties(u,registerRespDto);
                                return registerRespDto;
                            }));
                })
                        .map(ResultUtil::ofSuccess));
    }

    @Transactional(rollbackFor = Exception.class)
    public Mono<Result<Integer>> update(UpdateUserReqDto updateUserReqDto){
        return userRepository.findById(updateUserReqDto.getId())
                .flatMap(u -> {
                    User user = MyBeanUtil.copyPropertiesIngoreNull(User.class, u, updateUserReqDto);
                    return userRepository.save(user)
                            .flatMap(tempUser -> {
                                List<UserRoleDto> userRoleList = updateUserReqDto.getUserRoleList();
                                List<UserRole> userRoles = Optional.ofNullable(userRoleList)
                                        .map(list -> list.stream().map(roleItem -> {
                                            UserRole userRole = new UserRole();
                                            userRole.setRoleId(roleItem.getRoleId());
                                            userRole.setUid(u.getId());
                                            return userRole;
                                        }).collect(Collectors.toList()))
                                        .orElseGet(Collections::emptyList);
                                return userRoleRepository.deleteByUid(tempUser.getId())
                                        .then(Mono.just(userRoles)
                                                .filter(CollectionUtils::isNotEmpty)
                                                .flatMapMany(userRoleRepository::saveAll)
                                                .collectList())
                                        .map(idList -> 1);
                            });
                })
                .map(ResultUtil::ofSuccess)
                .switchIfEmpty(Mono.defer(() -> Mono.just(ResultUtil.ofFail("用户不存在！",Integer.class))));
    }

    @Transactional(rollbackFor = Exception.class)
    public Mono<Result<String>> deleteByIdOrEmail(Integer id,String email){
        if(id != null)
            return userRepository.findById(id)
                        .flatMap(v -> userRepository.deleteById(id)
                                .then(userRoleRepository.deleteByUid(id))
                                .then(Mono.defer(() -> Mono.just(ResultUtil.ofSuccess("删除成功！")))))
                        .switchIfEmpty(Mono.defer(() -> Mono.just(ResultUtil.ofNotFound("该用户不存在！",String.class))));
        return userRepository.findByEmail(email)
                .flatMap(v -> this.deleteByIdOrEmail(v.getId(),null));
    }

    public Mono<List<UserDto>> listUser(PageReqDto pageReqDto){
        return userRepository.findUserCustom((pageReqDto.getIndex() - 1)*pageReqDto.getSize(),pageReqDto.getSize())
                .map(u -> MyBeanUtil.copyProperties(u, UserDto.class))
                .collectList()
                .flatMap(list -> {
                    List<Integer> uidList = list.stream().map(UserDto::getId).collect(Collectors.toList());
                    return Mono.just(uidList)
                            .filter(CollectionUtils::isNotEmpty)
                            .flatMapMany(tempUidList -> userRoleRepository.findByUid(tempUidList,UserRoleBo.class))
                            .collectList()
                            .map(roleList -> {
                                Map<Integer, List<UserRoleDto>> roleMaps = roleList.stream().map(role -> MyBeanUtil.copyProperties(role, UserRoleDto.class)).collect(Collectors.groupingBy(UserRoleDto::getUid));
                                list.forEach(userDto ->{
                                    userDto.setRolesList(roleMaps.get(userDto.getId()));
                                });
                                return list;
                            }).switchIfEmpty(Mono.defer(() -> Mono.just(list)));
                });
    }

    public Flux<UserDto> listUser(Integer start, Integer pageSize){
        Pageable pageable = PageRequest.of(start,pageSize, Sort.by(new Sort.Order(Sort.Direction.DESC,"id")));
        return userRepository.findUserByIdGreaterThan(0,pageable)
                .map(u -> MyBeanUtil.copyProperties(u, UserDto.class));
    }

    public Mono<Result<LoginRespDto>> login(LoginReqDto loginReqDto) {
        return userRepository
                .findByEmail(loginReqDto.getEmail())
                .flatMap(u -> {
                    if(Objects.equals(u.getPassword(),loginReqDto.getPassword())){
                        return userRoleRepository.findByUid(Arrays.asList(u.getId()),UserRoleBo.class)
                                .map(UserRoleBo::getName)
                                .collectList()
                                .map(rList -> LoginRespDto.builder().id(u.getId()).userDto(MyBeanUtil.copyProperties(u,UserDto.class)).roleList(rList).build())
                                .switchIfEmpty(Mono.defer(() ->
                                                Mono.just(LoginRespDto.builder().id(u.getId()).build())))
                                .map(ResultUtil::ofSuccess);
                    }
                    return Mono.defer(() -> Mono.just("密码不正确！").map(msg -> ResultUtil.ofFail(msg,LoginRespDto.class)));
                }).switchIfEmpty(Mono.defer(() -> Mono.just("email不正确！").map(msg -> ResultUtil.ofFail(msg,LoginRespDto.class))));
    }

    public Mono<Result<UserInfoDto>> userInfo(Integer id) {
        return userRepository.findById(id)
                .zipWith(userRoleRepository.findByUid(Arrays.asList(id),UserRoleBo.class)
                        .map(UserRoleBo::getName)
                        .collectList())
                .map(tuple2 -> {
                    User user = tuple2.getT1();
                    List<String> roleList = tuple2.getT2();
                    UserInfoDto userInfoDto = new UserInfoDto();
                    userInfoDto.setId(id);
                    userInfoDto.setEmail(user.getEmail());
                    userInfoDto.setHeadUrl(user.getHeadUrl());
                    userInfoDto.setUsername(user.getUsername());
                    userInfoDto.setRoleList(roleList);
                    return userInfoDto;
                })
                .map(ResultUtil::ofSuccess)
                .switchIfEmpty(Mono.defer(()->Mono.just(ResultUtil.ofFail("用户不存在！",UserInfoDto.class))));
    }
}
